/*
 * Copyright 2009-2011 Oleg Mazurov, Circuits At Home, http://www.circuitsathome.com
 * MAX3421E USB host controller support
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the authors nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/* MAX3421E USB host controller support */

#include "Max3421e.h"
// #include "Max3421e_constants.h"

static byte vbusState;

/* Functions    */

#define INT		PE6
#define INT_PORT	PORTE
#define INT_DDR		DDRE
#define INT_PIN		PINE

#define RST		PJ2
#define RST_PORT	PORTJ
#define RST_DDR		DDRJ
#define RST_PIN		PINJ

#define GPX		PJ3
#define GPX_PORT	PORTJ
#define GPX_DDR		DDRJ
#define GPX_PIN		PINJ

void MAX3421E::setRST(uint8_t val) {
	if (val == LOW)
		RST_PORT &= ~_BV(RST);
	else
		RST_PORT |= _BV(RST);
}

uint8_t MAX3421E::readINT(void) {
	return INT_PIN & _BV(INT) ? HIGH : LOW;
}

uint8_t MAX3421E::readGPX(void) {
	// return GPX_PIN & _BV(GPX) ? HIGH : LOW;
	return LOW;
}

void MAX3421E::pinInit(void) {
	INT_DDR &= ~_BV(INT);
	RST_DDR |= _BV(RST);
	digitalWrite(MAX_SS, HIGH);
	setRST(HIGH);
}

/* Constructor */
MAX3421E::MAX3421E() {
	spi_init();
	pinInit();
}

byte MAX3421E::getVbusState(void) {
	return (vbusState);
}
/* initialization */
//void MAX3421E::init()
//{
//    /* setup pins */
//    pinMode( MAX_INT, INPUT);
//    pinMode( MAX_GPX, INPUT );
//    pinMode( MAX_SS, OUTPUT );
//    //pinMode( BPNT_0, OUTPUT );
//    //pinMode( BPNT_1, OUTPUT );
//    //digitalWrite( BPNT_0, LOW );
//    //digitalWrite( BPNT_1, LOW );
//    Deselect_MAX3421E;              
//    pinMode( MAX_RESET, OUTPUT );
//    digitalWrite( MAX_RESET, HIGH );  //release MAX3421E from reset
//}
//byte MAX3421E::getVbusState( void )
//{
//    return( vbusState );
//}
//void MAX3421E::toggle( byte pin )
//{
//    digitalWrite( pin, HIGH );
//    digitalWrite( pin, LOW );
//}
/* Single host register write   */
void MAX3421E::regWr(byte reg, byte val) {
	digitalWrite(MAX_SS, LOW);
	SPDR = (reg | 0x02);
	while (!( SPSR & (1 << SPIF)))
		;
	SPDR = val;
	while (!( SPSR & (1 << SPIF)))
		;
	digitalWrite(MAX_SS, HIGH);
	return;
}
/* multiple-byte write */
/* returns a pointer to a memory position after last written */
char * MAX3421E::bytesWr(byte reg, byte nbytes, char * data) {
	digitalWrite(MAX_SS, LOW);
	SPDR = (reg | 0x02);
	while (nbytes--) {
		while (!( SPSR & (1 << SPIF)))
			;  //check if previous byte was sent
		SPDR = (*data);               // send next data byte
		data++;                         // advance data pointer
	}
	while (!( SPSR & (1 << SPIF)))
		;
	digitalWrite(MAX_SS, HIGH);
	return (data);
}
/* GPIO write. GPIO byte is split between 2 registers, so two writes are needed to write one byte */
/* GPOUT bits are in the low nibble. 0-3 in IOPINS1, 4-7 in IOPINS2 */
/* upper 4 bits of IOPINS1, IOPINS2 are read-only, so no masking is necessary */
void MAX3421E::gpioWr(byte val) {
	regWr( rIOPINS1, val);
	val = val >> 4;
	regWr( rIOPINS2, val);

	return;
}
/* Single host register read        */
byte MAX3421E::regRd(byte reg) {
	digitalWrite(MAX_SS, LOW);
	SPDR = reg;
	while (!( SPSR & (1 << SPIF)))
		;
	SPDR = 0; //send empty byte
	while (!( SPSR & (1 << SPIF)))
		;
	digitalWrite(MAX_SS, HIGH);
	return ( SPDR);
}
/* multiple-bytes register read                             */
/* returns a pointer to a memory position after last read   */
char * MAX3421E::bytesRd(byte reg, byte nbytes, char * data) {
	digitalWrite(MAX_SS, LOW);
	SPDR = reg;
	while (!( SPSR & (1 << SPIF)))
		;    //wait
	while (nbytes) {
		SPDR = 0; //send empty byte
		nbytes--;
		while (!( SPSR & (1 << SPIF)))
			;
		*data = SPDR;
		data++;
	}
	digitalWrite(MAX_SS, HIGH);
	return (data);
}
/* GPIO read. See gpioWr for explanation */
/* GPIN pins are in high nibbles of IOPINS1, IOPINS2    */
byte MAX3421E::gpioRd(void) {
	byte tmpbyte = 0;
	tmpbyte = regRd( rIOPINS2);            //pins 4-7
	tmpbyte &= 0xf0;                        //clean lower nibble
	tmpbyte |= (regRd( rIOPINS1) >> 4);  //shift low bits and OR with upper from previous operation. Upper nibble zeroes during shift, at least with this compiler
	return (tmpbyte);
}
/* reset MAX3421E using chip reset bit. SPI configuration is not affected   */
boolean MAX3421E::reset() {
	byte tmp = 0;
	regWr( rUSBCTL, bmCHIPRES);                        //Chip reset. This stops the oscillator
	regWr( rUSBCTL, 0x00);                             //Remove the reset
	while (!(regRd( rUSBIRQ) & bmOSCOKIRQ)) {          //wait until the PLL is stable
		tmp++;                                          //timeout after 256 attempts
		if (tmp == 0) {
			return ( false);
		}
	}
	return ( true);
}
/* turn USB power on/off                                                */
/* does nothing, returns TRUE. Left for compatibility with old sketches               */
/* will be deleted eventually                                           */
///* ON pin of VBUS switch (MAX4793 or similar) is connected to GPOUT7    */
///* OVERLOAD pin of Vbus switch is connected to GPIN7                    */
///* OVERLOAD state low. NO OVERLOAD or VBUS OFF state high.              */
boolean MAX3421E::vbusPwr(boolean action) {
//  byte tmp;
//    tmp = regRd( rIOPINS2 );                //copy of IOPINS2
//    if( action ) {                          //turn on by setting GPOUT7
//        tmp |= bmGPOUT7;
//    }
//    else {                                  //turn off by clearing GPOUT7
//        tmp &= ~bmGPOUT7;
//    }
//    regWr( rIOPINS2, tmp );                 //send GPOUT7
//    if( action ) {
//        delay( 60 );
//    }
//    if (( regRd( rIOPINS2 ) & bmGPIN7 ) == 0 ) {     // check if overload is present. MAX4793 /FLAG ( pin 4 ) goes low if overload
//        return( false );
//    }                      
	return ( true);                                             // power on/off successful
}
/* probe bus to determine device presense and speed and switch host to this speed */
void MAX3421E::busprobe(void) {
	byte bus_sample;
	bus_sample = regRd( rHRSL);            //Get J,K status
	bus_sample &= ( bmJSTATUS | bmKSTATUS);      //zero the rest of the byte
	switch (bus_sample) {                          //start full-speed or low-speed host
	case ( bmJSTATUS):
		if ((regRd( rMODE) & bmLOWSPEED) == 0) {
			regWr( rMODE, MODE_FS_HOST);       //start full-speed host
			vbusState = FSHOST;
		} else {
			regWr( rMODE, MODE_LS_HOST);        //start low-speed host
			vbusState = LSHOST;
		}
		break;
	case ( bmKSTATUS):
		if ((regRd( rMODE) & bmLOWSPEED) == 0) {
			regWr( rMODE, MODE_LS_HOST);       //start low-speed host
			vbusState = LSHOST;
		} else {
			regWr( rMODE, MODE_FS_HOST);       //start full-speed host
			vbusState = FSHOST;
		}
		break;
	case ( bmSE1):              //illegal state
		vbusState = SE1;
		break;
	case ( bmSE0):              //disconnected state
		regWr( rMODE, bmDPPULLDN | bmDMPULLDN | bmHOST | bmSEPIRQ);
		vbusState = SE0;
		break;
	}              //end switch( bus_sample )
}
/* MAX3421E initialization after power-on   */
void MAX3421E::powerOn() {
	/* Configure full-duplex SPI, interrupt pulse   */
	regWr( rPINCTL, ( bmFDUPSPI + bmINTLEVEL + bmGPXB));    //Full-duplex SPI, level interrupt, GPX
	if (reset() == false) {                                //stop/start the oscillator
		Serial.println("Error: OSCOKIRQ failed to assert");
	}

	/* configure host operation */
	regWr( rMODE, bmDPPULLDN | bmDMPULLDN | bmHOST | bmSEPIRQ);      // set pull-downs, Host, Separate GPIN IRQ on GPX
	regWr( rHIEN, bmCONDETIE | bmFRAMEIE);                                             //connection detection
	/* check if device is connected */
	regWr( rHCTL, bmSAMPLEBUS);                                             // sample USB bus
	while (!(regRd( rHCTL) & bmSAMPLEBUS))
		;                                //wait for sample operation to finish
	busprobe();                                                             //check if anything is connected
	regWr( rHIRQ, bmCONDETIRQ);                                            //clear connection detect interrupt
	regWr( rCPUCTL, 0x01);                                                 //enable interrupt pin
}
/* MAX3421 state change task and interrupt handler */
byte MAX3421E::Task(void) {
	byte rcode = 0;
	byte pinvalue;
	//Serial.print("Vbus state: ");
	//Serial.println( vbusState, HEX );
	pinvalue = readINT();
	if (pinvalue == LOW) {
		rcode = IntHandler();
	}
	pinvalue = readGPX();
	if (pinvalue == LOW) {
		GpxHandler();
	}
//    usbSM();                                //USB state machine                            
	return (rcode);
}
byte MAX3421E::IntHandler() {
	byte HIRQ;
	byte HIRQ_sendback = 0x00;
	HIRQ = regRd( rHIRQ);                  //determine interrupt source
	//if( HIRQ & bmFRAMEIRQ ) {               //->1ms SOF interrupt handler
	//    HIRQ_sendback |= bmFRAMEIRQ;
	//}//end FRAMEIRQ handling
	if (HIRQ & bmCONDETIRQ) {
		busprobe();
		HIRQ_sendback |= bmCONDETIRQ;
	}
	/* End HIRQ interrupts handling, clear serviced IRQs    */
	regWr( rHIRQ, HIRQ_sendback);
	return (HIRQ_sendback);
}
byte MAX3421E::GpxHandler() {
	byte GPINIRQ = regRd( rGPINIRQ);          //read GPIN IRQ register
//    if( GPINIRQ & bmGPINIRQ7 ) {            //vbus overload
//        vbusPwr( OFF );                     //attempt powercycle
//        delay( 1000 );
//        vbusPwr( ON );
//        regWr( rGPINIRQ, bmGPINIRQ7 );
//    }       
	return (GPINIRQ);
}

//void MAX3421E::usbSM( void )                //USB state machine
//{
//    
//
//}
